// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/events/devices/x11/device_data_manager_x11.h"

#include <X11/Xlib.h>
#include <X11/extensions/XInput.h>
#include <X11/extensions/XInput2.h>
#include <stddef.h>

#include <utility>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/singleton.h"
#include "base/sys_info.h"
#include "build/build_config.h"
#include "ui/events/devices/keyboard_device.h"
#include "ui/events/devices/x11/device_list_cache_x11.h"
#include "ui/events/devices/x11/touch_factory_x11.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_switches.h"
#include "ui/events/keycodes/keyboard_code_conversion_x.h"
#include "ui/gfx/display.h"
#include "ui/gfx/geometry/point3_f.h"
#include "ui/gfx/x/x11_types.h"

// XIScrollClass was introduced in XI 2.1 so we need to define it here
// for backward-compatibility with older versions of XInput.
#if !defined(XIScrollClass)
#define XIScrollClass 3
#endif

// Multi-touch support was introduced in XI 2.2. Add XI event types here
// for backward-compatibility with older versions of XInput.
#if !defined(XI_TouchBegin)
#define XI_TouchBegin 18
#define XI_TouchUpdate 19
#define XI_TouchEnd 20
#endif

// Copied from xserver-properties.h
#define AXIS_LABEL_PROP_REL_HWHEEL "Rel Horiz Wheel"
#define AXIS_LABEL_PROP_REL_WHEEL "Rel Vert Wheel"

// CMT specific timings
#define AXIS_LABEL_PROP_ABS_DBL_START_TIME "Abs Dbl Start Timestamp"
#define AXIS_LABEL_PROP_ABS_DBL_END_TIME "Abs Dbl End Timestamp"

// Ordinal values
#define AXIS_LABEL_PROP_ABS_DBL_ORDINAL_X "Abs Dbl Ordinal X"
#define AXIS_LABEL_PROP_ABS_DBL_ORDINAL_Y "Abs Dbl Ordinal Y"

// Fling properties
#define AXIS_LABEL_PROP_ABS_DBL_FLING_VX "Abs Dbl Fling X Velocity"
#define AXIS_LABEL_PROP_ABS_DBL_FLING_VY "Abs Dbl Fling Y Velocity"
#define AXIS_LABEL_PROP_ABS_FLING_STATE "Abs Fling State"

#define AXIS_LABEL_PROP_ABS_FINGER_COUNT "Abs Finger Count"

// Cros metrics gesture from touchpad
#define AXIS_LABEL_PROP_ABS_METRICS_TYPE "Abs Metrics Type"
#define AXIS_LABEL_PROP_ABS_DBL_METRICS_DATA1 "Abs Dbl Metrics Data 1"
#define AXIS_LABEL_PROP_ABS_DBL_METRICS_DATA2 "Abs Dbl Metrics Data 2"

// Touchscreen multi-touch
#define AXIS_LABEL_ABS_MT_TOUCH_MAJOR "Abs MT Touch Major"
#define AXIS_LABEL_ABS_MT_TOUCH_MINOR "Abs MT Touch Minor"
#define AXIS_LABEL_ABS_MT_ORIENTATION "Abs MT Orientation"
#define AXIS_LABEL_ABS_MT_PRESSURE "Abs MT Pressure"
#define AXIS_LABEL_ABS_MT_POSITION_X "Abs MT Position X"
#define AXIS_LABEL_ABS_MT_POSITION_Y "Abs MT Position Y"
#define AXIS_LABEL_ABS_MT_TRACKING_ID "Abs MT Tracking ID"
#define AXIS_LABEL_TOUCH_TIMESTAMP "Touch Timestamp"

// When you add new data types, please make sure the order here is aligned
// with the order in the DataType enum in the header file because we assume
// they are in sync when updating the device list (see UpdateDeviceList).
const char* kCachedAtoms[] = {
    AXIS_LABEL_PROP_REL_HWHEEL,
    AXIS_LABEL_PROP_REL_WHEEL,
    AXIS_LABEL_PROP_ABS_DBL_ORDINAL_X,
    AXIS_LABEL_PROP_ABS_DBL_ORDINAL_Y,
    AXIS_LABEL_PROP_ABS_DBL_START_TIME,
    AXIS_LABEL_PROP_ABS_DBL_END_TIME,
    AXIS_LABEL_PROP_ABS_DBL_FLING_VX,
    AXIS_LABEL_PROP_ABS_DBL_FLING_VY,
    AXIS_LABEL_PROP_ABS_FLING_STATE,
    AXIS_LABEL_PROP_ABS_METRICS_TYPE,
    AXIS_LABEL_PROP_ABS_DBL_METRICS_DATA1,
    AXIS_LABEL_PROP_ABS_DBL_METRICS_DATA2,
    AXIS_LABEL_PROP_ABS_FINGER_COUNT,
    AXIS_LABEL_ABS_MT_TOUCH_MAJOR,
    AXIS_LABEL_ABS_MT_TOUCH_MINOR,
    AXIS_LABEL_ABS_MT_ORIENTATION,
    AXIS_LABEL_ABS_MT_PRESSURE,
    AXIS_LABEL_ABS_MT_POSITION_X,
    AXIS_LABEL_ABS_MT_POSITION_Y,
    AXIS_LABEL_ABS_MT_TRACKING_ID,
    AXIS_LABEL_TOUCH_TIMESTAMP,

    NULL
};

// Constants for checking if a data type lies in the range of CMT/Touch data
// types.
const int kCMTDataTypeStart = ui::DeviceDataManagerX11::DT_CMT_SCROLL_X;
const int kCMTDataTypeEnd = ui::DeviceDataManagerX11::DT_CMT_FINGER_COUNT;
const int kTouchDataTypeStart = ui::DeviceDataManagerX11::DT_TOUCH_MAJOR;
const int kTouchDataTypeEnd = ui::DeviceDataManagerX11::DT_TOUCH_RAW_TIMESTAMP;

namespace ui {

namespace {

    template <typename Iterator>
    Iterator FindDeviceWithId(Iterator begin, Iterator end, int id)
    {
        for (auto it = begin; it != end; ++it) {
            if (it->id == id)
                return it;
        }
        return end;
    }

} // namespace

bool DeviceDataManagerX11::IsCMTDataType(const int type)
{
    return (type >= kCMTDataTypeStart) && (type <= kCMTDataTypeEnd);
}

bool DeviceDataManagerX11::IsTouchDataType(const int type)
{
    return (type >= kTouchDataTypeStart) && (type <= kTouchDataTypeEnd);
}

// static
void DeviceDataManagerX11::CreateInstance()
{
    if (instance())
        return;

    DeviceDataManagerX11* device_data_manager = new DeviceDataManagerX11();

    // TODO(bruthig): Replace the DeleteInstance callbacks with explicit calls.
    base::AtExitManager::RegisterTask(
        base::Bind(DeviceDataManager::DeleteInstance));

    set_instance(device_data_manager);
}

// static
DeviceDataManagerX11* DeviceDataManagerX11::GetInstance()
{
    return static_cast<DeviceDataManagerX11*>(DeviceDataManager::GetInstance());
}

DeviceDataManagerX11::DeviceDataManagerX11()
    : xi_opcode_(-1)
    , atom_cache_(gfx::GetXDisplay(), kCachedAtoms)
    , button_map_count_(0)
{
    CHECK(gfx::GetXDisplay());
    InitializeXInputInternal();

    // Make sure the sizes of enum and kCachedAtoms are aligned.
    CHECK(arraysize(kCachedAtoms) == static_cast<size_t>(DT_LAST_ENTRY) + 1);
    UpdateDeviceList(gfx::GetXDisplay());
    UpdateButtonMap();
}

DeviceDataManagerX11::~DeviceDataManagerX11()
{
}

bool DeviceDataManagerX11::InitializeXInputInternal()
{
    // Check if XInput is available on the system.
    xi_opcode_ = -1;
    int opcode, event, error;
    if (!XQueryExtension(
            gfx::GetXDisplay(), "XInputExtension", &opcode, &event, &error)) {
        VLOG(1) << "X Input extension not available: error=" << error;
        return false;
    }

    // Check the XInput version.
    int major = 2, minor = 2;
    if (XIQueryVersion(gfx::GetXDisplay(), &major, &minor) == BadRequest) {
        VLOG(1) << "XInput2 not supported in the server.";
        return false;
    }
    if (major < 2 || (major == 2 && minor < 2)) {
        DVLOG(1) << "XI version on server is " << major << "." << minor << ". "
                 << "But 2.2 is required.";
        return false;
    }

    xi_opcode_ = opcode;
    CHECK_NE(-1, xi_opcode_);

    // Possible XI event types for XIDeviceEvent. See the XI2 protocol
    // specification.
    xi_device_event_types_[XI_KeyPress] = true;
    xi_device_event_types_[XI_KeyRelease] = true;
    xi_device_event_types_[XI_ButtonPress] = true;
    xi_device_event_types_[XI_ButtonRelease] = true;
    xi_device_event_types_[XI_Motion] = true;
    // Multi-touch support was introduced in XI 2.2.
    if (minor >= 2) {
        xi_device_event_types_[XI_TouchBegin] = true;
        xi_device_event_types_[XI_TouchUpdate] = true;
        xi_device_event_types_[XI_TouchEnd] = true;
    }
    return true;
}

bool DeviceDataManagerX11::IsXInput2Available() const
{
    return xi_opcode_ != -1;
}

void DeviceDataManagerX11::UpdateDeviceList(Display* display)
{
    cmt_devices_.reset();
    touchpads_.reset();
    scrollclass_devices_.reset();
    master_pointers_.clear();
    for (int i = 0; i < kMaxDeviceNum; ++i) {
        valuator_count_[i] = 0;
        valuator_lookup_[i].clear();
        data_type_lookup_[i].clear();
        valuator_min_[i].clear();
        valuator_max_[i].clear();
        scroll_data_[i].horizontal.number = -1;
        scroll_data_[i].horizontal.seen = false;
        scroll_data_[i].vertical.number = -1;
        scroll_data_[i].vertical.seen = false;
        for (int j = 0; j < kMaxSlotNum; j++)
            last_seen_valuator_[i][j].clear();
    }

    // Find all the touchpad devices.
    const XDeviceList& dev_list = ui::DeviceListCacheX11::GetInstance()->GetXDeviceList(display);
    Atom xi_touchpad = XInternAtom(display, XI_TOUCHPAD, false);
    for (int i = 0; i < dev_list.count; ++i)
        if (dev_list[i].type == xi_touchpad)
            touchpads_[dev_list[i].id] = true;

    if (!IsXInput2Available())
        return;

    // Update the structs with new valuator information
    const XIDeviceList& info_list = ui::DeviceListCacheX11::GetInstance()->GetXI2DeviceList(display);
    Atom atoms[DT_LAST_ENTRY];
    for (int data_type = 0; data_type < DT_LAST_ENTRY; ++data_type)
        atoms[data_type] = atom_cache_.GetAtom(kCachedAtoms[data_type]);

    for (int i = 0; i < info_list.count; ++i) {
        const XIDeviceInfo& info = info_list[i];

        if (info.use == XIMasterPointer)
            master_pointers_.push_back(info.deviceid);

        // We currently handle only slave, non-keyboard devices
        if (info.use != XISlavePointer && info.use != XIFloatingSlave)
            continue;

        bool possible_cmt = false;
        bool not_cmt = false;
        const int deviceid = info.deviceid;

        for (int j = 0; j < info.num_classes; ++j) {
            if (info.classes[j]->type == XIValuatorClass)
                ++valuator_count_[deviceid];
            else if (info.classes[j]->type == XIScrollClass)
                not_cmt = true;
        }

        // Skip devices that don't use any valuator
        if (!valuator_count_[deviceid])
            continue;

        valuator_lookup_[deviceid].resize(DT_LAST_ENTRY, -1);
        data_type_lookup_[deviceid].resize(
            valuator_count_[deviceid], DT_LAST_ENTRY);
        valuator_min_[deviceid].resize(DT_LAST_ENTRY, 0);
        valuator_max_[deviceid].resize(DT_LAST_ENTRY, 0);
        for (int j = 0; j < kMaxSlotNum; j++)
            last_seen_valuator_[deviceid][j].resize(DT_LAST_ENTRY, 0);
        for (int j = 0; j < info.num_classes; ++j) {
            if (info.classes[j]->type == XIValuatorClass) {
                if (UpdateValuatorClassDevice(
                        reinterpret_cast<XIValuatorClassInfo*>(info.classes[j]), atoms,
                        deviceid))
                    possible_cmt = true;
            } else if (info.classes[j]->type == XIScrollClass) {
                UpdateScrollClassDevice(
                    reinterpret_cast<XIScrollClassInfo*>(info.classes[j]), deviceid);
            }
        }

        if (possible_cmt && !not_cmt)
            cmt_devices_[deviceid] = true;
    }
}

bool DeviceDataManagerX11::GetSlotNumber(const XIDeviceEvent* xiev, int* slot)
{
    ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
    if (!factory->IsMultiTouchDevice(xiev->sourceid)) {
        *slot = 0;
        return true;
    }
    return factory->QuerySlotForTrackingID(xiev->detail, slot);
}

void DeviceDataManagerX11::GetEventRawData(const XEvent& xev, EventData* data)
{
    if (xev.type != GenericEvent)
        return;

    XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(xev.xcookie.data);
    CHECK_GE(xiev->sourceid, 0);
    CHECK_GE(xiev->deviceid, 0);
    if (xiev->sourceid >= kMaxDeviceNum || xiev->deviceid >= kMaxDeviceNum)
        return;
    data->clear();
    const int sourceid = xiev->sourceid;
    double* valuators = xiev->valuators.values;
    for (int i = 0; i <= valuator_count_[sourceid]; ++i) {
        if (XIMaskIsSet(xiev->valuators.mask, i)) {
            int type = data_type_lookup_[sourceid][i];
            if (type != DT_LAST_ENTRY) {
                (*data)[type] = *valuators;
                if (IsTouchDataType(type)) {
                    int slot = -1;
                    if (GetSlotNumber(xiev, &slot) && slot >= 0 && slot < kMaxSlotNum)
                        last_seen_valuator_[sourceid][slot][type] = *valuators;
                }
            }
            valuators++;
        }
    }
}

bool DeviceDataManagerX11::GetEventData(const XEvent& xev,
    const DataType type, double* value)
{
    if (xev.type != GenericEvent)
        return false;

    XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(xev.xcookie.data);
    CHECK_GE(xiev->sourceid, 0);
    CHECK_GE(xiev->deviceid, 0);
    if (xiev->sourceid >= kMaxDeviceNum || xiev->deviceid >= kMaxDeviceNum)
        return false;
    const int sourceid = xiev->sourceid;
    if (valuator_lookup_[sourceid].empty())
        return false;

    if (type == DT_TOUCH_TRACKING_ID) {
        // With XInput2 MT, Tracking ID is provided in the detail field for touch
        // events.
        if (xiev->evtype == XI_TouchBegin || xiev->evtype == XI_TouchEnd || xiev->evtype == XI_TouchUpdate) {
            *value = xiev->detail;
        } else {
            *value = 0;
        }
        return true;
    }

    int val_index = valuator_lookup_[sourceid][type];
    int slot = 0;
    if (val_index >= 0) {
        if (XIMaskIsSet(xiev->valuators.mask, val_index)) {
            double* valuators = xiev->valuators.values;
            while (val_index--) {
                if (XIMaskIsSet(xiev->valuators.mask, val_index))
                    ++valuators;
            }
            *value = *valuators;
            if (IsTouchDataType(type)) {
                if (GetSlotNumber(xiev, &slot) && slot >= 0 && slot < kMaxSlotNum)
                    last_seen_valuator_[sourceid][slot][type] = *value;
            }
            return true;
        } else if (IsTouchDataType(type)) {
            if (GetSlotNumber(xiev, &slot) && slot >= 0 && slot < kMaxSlotNum)
                *value = last_seen_valuator_[sourceid][slot][type];
        }
    }

    return false;
}

bool DeviceDataManagerX11::IsXIDeviceEvent(const XEvent& xev) const
{
    if (xev.type != GenericEvent || xev.xcookie.extension != xi_opcode_)
        return false;
    return xi_device_event_types_[xev.xcookie.evtype];
}

bool DeviceDataManagerX11::IsTouchpadXInputEvent(const XEvent& xev) const
{
    if (xev.type != GenericEvent)
        return false;

    XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev.xcookie.data);
    CHECK_GE(xievent->sourceid, 0);
    if (xievent->sourceid >= kMaxDeviceNum)
        return false;
    return touchpads_[xievent->sourceid];
}

bool DeviceDataManagerX11::IsCMTDeviceEvent(const XEvent& xev) const
{
    if (xev.type != GenericEvent)
        return false;

    XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev.xcookie.data);
    CHECK_GE(xievent->sourceid, 0);
    if (xievent->sourceid >= kMaxDeviceNum)
        return false;
    return cmt_devices_[xievent->sourceid];
}

int DeviceDataManagerX11::GetScrollClassEventDetail(const XEvent& xev) const
{
    if (xev.type != GenericEvent)
        return SCROLL_TYPE_NO_SCROLL;

    XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev.xcookie.data);
    if (xievent->sourceid >= kMaxDeviceNum)
        return SCROLL_TYPE_NO_SCROLL;
    if (!scrollclass_devices_[xievent->sourceid])
        return SCROLL_TYPE_NO_SCROLL;
    int horizontal_id = scroll_data_[xievent->sourceid].horizontal.number;
    int vertical_id = scroll_data_[xievent->sourceid].vertical.number;
    return (XIMaskIsSet(xievent->valuators.mask, horizontal_id)
                   ? SCROLL_TYPE_HORIZONTAL
                   : 0)
        | (XIMaskIsSet(xievent->valuators.mask, vertical_id)
                ? SCROLL_TYPE_VERTICAL
                : 0);
}

int DeviceDataManagerX11::GetScrollClassDeviceDetail(const XEvent& xev) const
{
    if (xev.type != GenericEvent)
        return SCROLL_TYPE_NO_SCROLL;

    XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(xev.xcookie.data);
    if (xiev->sourceid >= kMaxDeviceNum || xiev->deviceid >= kMaxDeviceNum)
        return SCROLL_TYPE_NO_SCROLL;
    const int sourceid = xiev->sourceid;
    const ScrollInfo& device_data = scroll_data_[sourceid];
    return (device_data.vertical.number >= 0 ? SCROLL_TYPE_VERTICAL : 0) | (device_data.horizontal.number >= 0 ? SCROLL_TYPE_HORIZONTAL : 0);
}

bool DeviceDataManagerX11::IsCMTGestureEvent(const XEvent& xev) const
{
    return (IsScrollEvent(xev) || IsFlingEvent(xev) || IsCMTMetricsEvent(xev));
}

bool DeviceDataManagerX11::HasEventData(
    const XIDeviceEvent* xiev, const DataType type) const
{
    CHECK_GE(xiev->sourceid, 0);
    if (xiev->sourceid >= kMaxDeviceNum)
        return false;
    if (type >= valuator_lookup_[xiev->sourceid].size())
        return false;
    const int idx = valuator_lookup_[xiev->sourceid][type];
    return (idx >= 0) && XIMaskIsSet(xiev->valuators.mask, idx);
}

bool DeviceDataManagerX11::IsScrollEvent(const XEvent& xev) const
{
    if (!IsCMTDeviceEvent(xev))
        return false;

    XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(xev.xcookie.data);
    return (HasEventData(xiev, DT_CMT_SCROLL_X) || HasEventData(xiev, DT_CMT_SCROLL_Y));
}

bool DeviceDataManagerX11::IsFlingEvent(const XEvent& xev) const
{
    if (!IsCMTDeviceEvent(xev))
        return false;

    XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(xev.xcookie.data);
    return (HasEventData(xiev, DT_CMT_FLING_X) && HasEventData(xiev, DT_CMT_FLING_Y) && HasEventData(xiev, DT_CMT_FLING_STATE));
}

bool DeviceDataManagerX11::IsCMTMetricsEvent(const XEvent& xev) const
{
    if (!IsCMTDeviceEvent(xev))
        return false;

    XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(xev.xcookie.data);
    return (HasEventData(xiev, DT_CMT_METRICS_TYPE) && HasEventData(xiev, DT_CMT_METRICS_DATA1) && HasEventData(xiev, DT_CMT_METRICS_DATA2));
}

bool DeviceDataManagerX11::HasGestureTimes(const XEvent& xev) const
{
    if (!IsCMTDeviceEvent(xev))
        return false;

    XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(xev.xcookie.data);
    return (HasEventData(xiev, DT_CMT_START_TIME) && HasEventData(xiev, DT_CMT_END_TIME));
}

void DeviceDataManagerX11::GetScrollOffsets(const XEvent& xev,
    float* x_offset,
    float* y_offset,
    float* x_offset_ordinal,
    float* y_offset_ordinal,
    int* finger_count)
{
    *x_offset = 0;
    *y_offset = 0;
    *x_offset_ordinal = 0;
    *y_offset_ordinal = 0;
    *finger_count = 2;

    EventData data;
    GetEventRawData(xev, &data);

    if (data.find(DT_CMT_SCROLL_X) != data.end())
        *x_offset = data[DT_CMT_SCROLL_X];
    if (data.find(DT_CMT_SCROLL_Y) != data.end())
        *y_offset = data[DT_CMT_SCROLL_Y];
    if (data.find(DT_CMT_ORDINAL_X) != data.end())
        *x_offset_ordinal = data[DT_CMT_ORDINAL_X];
    if (data.find(DT_CMT_ORDINAL_Y) != data.end())
        *y_offset_ordinal = data[DT_CMT_ORDINAL_Y];
    if (data.find(DT_CMT_FINGER_COUNT) != data.end())
        *finger_count = static_cast<int>(data[DT_CMT_FINGER_COUNT]);
}

void DeviceDataManagerX11::GetScrollClassOffsets(const XEvent& xev,
    double* x_offset,
    double* y_offset)
{
    DCHECK_NE(SCROLL_TYPE_NO_SCROLL, GetScrollClassDeviceDetail(xev));

    *x_offset = 0;
    *y_offset = 0;

    if (xev.type != GenericEvent)
        return;

    XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(xev.xcookie.data);
    if (xiev->sourceid >= kMaxDeviceNum || xiev->deviceid >= kMaxDeviceNum)
        return;
    const int sourceid = xiev->sourceid;
    double* valuators = xiev->valuators.values;

    ScrollInfo* info = &scroll_data_[sourceid];

    const int horizontal_number = info->horizontal.number;
    const int vertical_number = info->vertical.number;

    for (int i = 0; i <= valuator_count_[sourceid]; ++i) {
        if (!XIMaskIsSet(xiev->valuators.mask, i))
            continue;
        if (i == horizontal_number) {
            *x_offset = ExtractAndUpdateScrollOffset(&info->horizontal, *valuators);
        } else if (i == vertical_number) {
            *y_offset = ExtractAndUpdateScrollOffset(&info->vertical, *valuators);
        }
        valuators++;
    }
}

void DeviceDataManagerX11::InvalidateScrollClasses()
{
    for (int i = 0; i < kMaxDeviceNum; i++) {
        scroll_data_[i].horizontal.seen = false;
        scroll_data_[i].vertical.seen = false;
    }
}

void DeviceDataManagerX11::GetFlingData(const XEvent& xev,
    float* vx,
    float* vy,
    float* vx_ordinal,
    float* vy_ordinal,
    bool* is_cancel)
{
    *vx = 0;
    *vy = 0;
    *vx_ordinal = 0;
    *vy_ordinal = 0;
    *is_cancel = false;

    EventData data;
    GetEventRawData(xev, &data);

    if (data.find(DT_CMT_FLING_X) != data.end())
        *vx = data[DT_CMT_FLING_X];
    if (data.find(DT_CMT_FLING_Y) != data.end())
        *vy = data[DT_CMT_FLING_Y];
    if (data.find(DT_CMT_FLING_STATE) != data.end())
        *is_cancel = !!static_cast<unsigned int>(data[DT_CMT_FLING_STATE]);
    if (data.find(DT_CMT_ORDINAL_X) != data.end())
        *vx_ordinal = data[DT_CMT_ORDINAL_X];
    if (data.find(DT_CMT_ORDINAL_Y) != data.end())
        *vy_ordinal = data[DT_CMT_ORDINAL_Y];
}

void DeviceDataManagerX11::GetMetricsData(const XEvent& xev,
    GestureMetricsType* type,
    float* data1,
    float* data2)
{
    *type = kGestureMetricsTypeUnknown;
    *data1 = 0;
    *data2 = 0;

    EventData data;
    GetEventRawData(xev, &data);

    if (data.find(DT_CMT_METRICS_TYPE) != data.end()) {
        int val = static_cast<int>(data[DT_CMT_METRICS_TYPE]);
        if (val == 0)
            *type = kGestureMetricsTypeNoisyGround;
        else
            *type = kGestureMetricsTypeUnknown;
    }
    if (data.find(DT_CMT_METRICS_DATA1) != data.end())
        *data1 = data[DT_CMT_METRICS_DATA1];
    if (data.find(DT_CMT_METRICS_DATA2) != data.end())
        *data2 = data[DT_CMT_METRICS_DATA2];
}

int DeviceDataManagerX11::GetMappedButton(int button)
{
    return button > 0 && button <= button_map_count_ ? button_map_[button - 1] : button;
}

void DeviceDataManagerX11::UpdateButtonMap()
{
    button_map_count_ = XGetPointerMapping(gfx::GetXDisplay(),
        button_map_,
        arraysize(button_map_));
}

void DeviceDataManagerX11::GetGestureTimes(const XEvent& xev,
    double* start_time,
    double* end_time)
{
    *start_time = 0;
    *end_time = 0;

    EventData data;
    GetEventRawData(xev, &data);

    if (data.find(DT_CMT_START_TIME) != data.end())
        *start_time = data[DT_CMT_START_TIME];
    if (data.find(DT_CMT_END_TIME) != data.end())
        *end_time = data[DT_CMT_END_TIME];
}

bool DeviceDataManagerX11::NormalizeData(int deviceid,
    const DataType type,
    double* value)
{
    double max_value;
    double min_value;
    if (GetDataRange(deviceid, type, &min_value, &max_value)) {
        *value = (*value - min_value) / (max_value - min_value);
        DCHECK(*value >= 0.0 && *value <= 1.0);
        return true;
    }
    return false;
}

bool DeviceDataManagerX11::GetDataRange(int deviceid,
    const DataType type,
    double* min,
    double* max)
{
    CHECK_GE(deviceid, 0);
    if (deviceid >= kMaxDeviceNum)
        return false;
    if (valuator_lookup_[deviceid][type] >= 0) {
        *min = valuator_min_[deviceid][type];
        *max = valuator_max_[deviceid][type];
        return true;
    }
    return false;
}

void DeviceDataManagerX11::SetDeviceListForTest(
    const std::vector<int>& touchscreen,
    const std::vector<int>& cmt_devices,
    const std::vector<int>& other_devices)
{
    for (int i = 0; i < kMaxDeviceNum; ++i) {
        valuator_count_[i] = 0;
        valuator_lookup_[i].clear();
        data_type_lookup_[i].clear();
        valuator_min_[i].clear();
        valuator_max_[i].clear();
        for (int j = 0; j < kMaxSlotNum; j++)
            last_seen_valuator_[i][j].clear();
    }

    for (int deviceid : touchscreen) {
        InitializeValuatorsForTest(deviceid, kTouchDataTypeStart, kTouchDataTypeEnd,
            0, 1000);
    }

    cmt_devices_.reset();
    for (int deviceid : cmt_devices) {
        cmt_devices_[deviceid] = true;
        touchpads_[deviceid] = true;
        InitializeValuatorsForTest(deviceid, kCMTDataTypeStart, kCMTDataTypeEnd,
            -1000, 1000);
    }

    for (int deviceid : other_devices) {
        InitializeValuatorsForTest(deviceid, kCMTDataTypeStart, kCMTDataTypeEnd,
            -1000, 1000);
    }
}

void DeviceDataManagerX11::SetValuatorDataForTest(XIDeviceEvent* xievent,
    DataType type,
    double value)
{
    int index = valuator_lookup_[xievent->deviceid][type];
    CHECK(!XIMaskIsSet(xievent->valuators.mask, index));
    CHECK(index >= 0 && index < valuator_count_[xievent->deviceid]);
    XISetMask(xievent->valuators.mask, index);

    double* valuators = xievent->valuators.values;
    for (int i = 0; i < index; ++i) {
        if (XIMaskIsSet(xievent->valuators.mask, i))
            valuators++;
    }
    for (int i = DT_LAST_ENTRY - 1; i > valuators - xievent->valuators.values;
         --i)
        xievent->valuators.values[i] = xievent->valuators.values[i - 1];
    *valuators = value;
}

void DeviceDataManagerX11::InitializeValuatorsForTest(int deviceid,
    int start_valuator,
    int end_valuator,
    double min_value,
    double max_value)
{
    valuator_lookup_[deviceid].resize(DT_LAST_ENTRY, -1);
    data_type_lookup_[deviceid].resize(DT_LAST_ENTRY, DT_LAST_ENTRY);
    valuator_min_[deviceid].resize(DT_LAST_ENTRY, 0);
    valuator_max_[deviceid].resize(DT_LAST_ENTRY, 0);
    for (int j = 0; j < kMaxSlotNum; j++)
        last_seen_valuator_[deviceid][j].resize(DT_LAST_ENTRY, 0);
    for (int j = start_valuator; j <= end_valuator; ++j) {
        valuator_lookup_[deviceid][j] = valuator_count_[deviceid];
        data_type_lookup_[deviceid][valuator_count_[deviceid]] = j;
        valuator_min_[deviceid][j] = min_value;
        valuator_max_[deviceid][j] = max_value;
        valuator_count_[deviceid]++;
    }
}

bool DeviceDataManagerX11::UpdateValuatorClassDevice(
    XIValuatorClassInfo* valuator_class_info,
    Atom* atoms,
    int deviceid)
{
    DCHECK(deviceid >= 0 && deviceid < kMaxDeviceNum);
    Atom* label = std::find(atoms, atoms + DT_LAST_ENTRY, valuator_class_info->label);
    if (label == atoms + DT_LAST_ENTRY) {
        return false;
    }
    int data_type = label - atoms;
    DCHECK_GE(data_type, 0);
    DCHECK_LT(data_type, DT_LAST_ENTRY);

    valuator_lookup_[deviceid][data_type] = valuator_class_info->number;
    data_type_lookup_[deviceid][valuator_class_info->number] = data_type;
    valuator_min_[deviceid][data_type] = valuator_class_info->min;
    valuator_max_[deviceid][data_type] = valuator_class_info->max;
    return IsCMTDataType(data_type);
}

void DeviceDataManagerX11::UpdateScrollClassDevice(
    XIScrollClassInfo* scroll_class_info,
    int deviceid)
{
    DCHECK(deviceid >= 0 && deviceid < kMaxDeviceNum);
    ScrollInfo& info = scroll_data_[deviceid];
    switch (scroll_class_info->scroll_type) {
    case XIScrollTypeVertical:
        info.vertical.number = scroll_class_info->number;
        info.vertical.increment = scroll_class_info->increment;
        info.vertical.position = 0;
        info.vertical.seen = false;
        break;
    case XIScrollTypeHorizontal:
        info.horizontal.number = scroll_class_info->number;
        info.horizontal.increment = scroll_class_info->increment;
        info.horizontal.position = 0;
        info.horizontal.seen = false;
        break;
    }
    scrollclass_devices_[deviceid] = true;
}

double DeviceDataManagerX11::ExtractAndUpdateScrollOffset(
    ScrollInfo::AxisInfo* axis,
    double valuator) const
{
    double offset = 0;
    if (axis->seen)
        offset = axis->position - valuator;
    axis->seen = true;
    axis->position = valuator;
    return offset / axis->increment;
}

void DeviceDataManagerX11::SetDisabledKeyboardAllowedKeys(
    scoped_ptr<std::set<KeyboardCode>> excepted_keys)
{
    DCHECK(!excepted_keys.get() || !blocked_keyboard_allowed_keys_.get());
    blocked_keyboard_allowed_keys_ = std::move(excepted_keys);
}

void DeviceDataManagerX11::DisableDevice(int deviceid)
{
    blocked_devices_.set(deviceid, true);
    // TODO(rsadam@): Support blocking touchscreen devices.
    std::vector<KeyboardDevice> keyboards = keyboard_devices();
    std::vector<KeyboardDevice>::iterator it = FindDeviceWithId(keyboards.begin(), keyboards.end(), deviceid);
    if (it != std::end(keyboards)) {
        blocked_keyboards_.insert(
            std::pair<int, KeyboardDevice>(deviceid, *it));
        keyboards.erase(it);
        DeviceDataManager::OnKeyboardDevicesUpdated(keyboards);
    }
}

void DeviceDataManagerX11::EnableDevice(int deviceid)
{
    blocked_devices_.set(deviceid, false);
    std::map<int, KeyboardDevice>::iterator it = blocked_keyboards_.find(deviceid);
    if (it != blocked_keyboards_.end()) {
        std::vector<KeyboardDevice> devices = keyboard_devices();
        // Add device to current list of active devices.
        devices.push_back((*it).second);
        blocked_keyboards_.erase(it);
        DeviceDataManager::OnKeyboardDevicesUpdated(devices);
    }
}

bool DeviceDataManagerX11::IsDeviceEnabled(int device_id) const
{
    return blocked_devices_.test(device_id);
}

bool DeviceDataManagerX11::IsEventBlocked(const XEvent& xev)
{
    // Only check XI2 events which have a source device id.
    if (xev.type != GenericEvent)
        return false;

    XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev.xcookie.data);
    // Allow any key events from blocked_keyboard_allowed_keys_.
    if (blocked_keyboard_allowed_keys_ && (xievent->evtype == XI_KeyPress || xievent->evtype == XI_KeyRelease) && blocked_keyboard_allowed_keys_->find(KeyboardCodeFromXKeyEvent(&xev)) != blocked_keyboard_allowed_keys_->end()) {
        return false;
    }

    return blocked_devices_.test(xievent->sourceid);
}

void DeviceDataManagerX11::OnKeyboardDevicesUpdated(
    const std::vector<KeyboardDevice>& devices)
{
    std::vector<KeyboardDevice> keyboards(devices);
    for (std::map<int, KeyboardDevice>::iterator blocked_iter = blocked_keyboards_.begin();
         blocked_iter != blocked_keyboards_.end();) {
        // Check if the blocked device still exists in list of devices.
        int device_id = blocked_iter->first;
        std::vector<KeyboardDevice>::iterator it = FindDeviceWithId(keyboards.begin(), keyboards.end(), device_id);
        // If the device no longer exists, unblock it, else filter it out from our
        // active list.
        if (it == keyboards.end()) {
            blocked_devices_.set((*blocked_iter).first, false);
            blocked_keyboards_.erase(blocked_iter++);
        } else {
            keyboards.erase(it);
            ++blocked_iter;
        }
    }
    // Notify base class of updated list.
    DeviceDataManager::OnKeyboardDevicesUpdated(keyboards);
}

} // namespace ui
